﻿using System;
using System.Threading.Tasks;

namespace Mrkz.ImageManipulation.SpatialFiltering
{
    public class SpatialFilterEngine<TPixel>
        where TPixel : IArithmeticOperation<TPixel>, IEquatable<TPixel>, IEquatableWithTolerance<TPixel>, IPixel, new()
    {
        SpatialFilterKernel _kernel_R = null;
        SpatialFilterKernel _kernel_G = null;
        SpatialFilterKernel _kernel_B = null;

        ParametricFilterKernelBase _paramkernel = null;
        private MaskFilterBase _mask;

        public SpatialFilterEngine(SpatialFilterKernel Kernel)
        {
            _kernel_R = Kernel;
            _kernel_G = Kernel;
            _kernel_B = Kernel;
        }
        public SpatialFilterEngine(SpatialFilterKernel Kernel, ParametricFilterKernelBase ParametricKernel)
        {
            _kernel_R = Kernel;
            _kernel_G = Kernel;
            _kernel_B = Kernel;
            _paramkernel = ParametricKernel;
        }
        public SpatialFilterEngine(SpatialFilterKernel Kernel_R, SpatialFilterKernel Kernel_G, SpatialFilterKernel Kernel_B, ParametricFilterKernelBase ParametricKernel)
        {
            _paramkernel = ParametricKernel;
            _kernel_R = Kernel_R;
            _kernel_G = Kernel_G;
            _kernel_B = Kernel_B;
        }

        public SpatialFilterEngine(SpatialFilterKernel Kernel_R, SpatialFilterKernel Kernel_G, SpatialFilterKernel Kernel_B, ParametricFilterKernelBase ParametricKernel, MaskFilterBase Mask)
        {
            _mask = Mask;
            _paramkernel = ParametricKernel;
            _kernel_R = Kernel_R;
            _kernel_G = Kernel_G;
            _kernel_B = Kernel_B;
        }

        public TImage<TPixel> ParallelCalculateFilter(TImage<TPixel> InputImage)
        {
            TImage<TPixel> output = ImageConverter<TPixel,TPixel>.FromImageWithCrop(InputImage);

            //for (int iRow=0;iRow<InputImage.Height;iRow++)
            Parallel.For(0, InputImage.Height, (iRow) =>
            {
                //every column
                for (int iCol = 0; iCol < InputImage.Width; iCol++)
                {
                    SpatialFilterEngineState state = new SpatialFilterEngineState(iCol, iRow, InputImage.Width, InputImage.Height);

                    SpatialFilterKernel KR, KG, KB;
                    if (_paramkernel != null)
                    {
                        KR = _paramkernel.ApplyToSpatialFilter(_kernel_R, state);
                        KG = _paramkernel.ApplyToSpatialFilter(_kernel_G, state);
                        KB = _paramkernel.ApplyToSpatialFilter(_kernel_B, state);
                    }
                    else
                    {
                        KR = _kernel_R;
                        KG = _kernel_G;
                        KB = _kernel_B;
                    }

                    if (_mask != null)
                    {
                        KR = _mask.ApplyToSpatialFilter(KR, state);
                        KG = _mask.ApplyToSpatialFilter(KG, state);
                        KB = _mask.ApplyToSpatialFilter(KB, state);
                    }

                    output.SetPixel(iRow, iCol, PixelConvolution2D(InputImage, state, KR, KG, KB));
                }
            }
            );

            return output;
        }

        private static TPixel PixelConvolution2D(TImage<TPixel> InputImage,
            SpatialFilterEngineState State, SpatialFilterKernel KernelR, SpatialFilterKernel KernelG, SpatialFilterKernel KernelB)
        {
            TPixel MPixel=new TPixel();

            //flipped index from high to low
            for (int kr = KernelR.HighSize; kr >= KernelR.LowSize; kr--)
            {
                for (int kc = KernelR.HighSize; kc >= KernelR.LowSize; kc--)
                {
                    int iC = State.CurrentColumn + kc;
                    int iR = State.CurrentRow + kr;

                    if (InputImage.IsValidPixelCoords(iR, iC))
                    {
                        TPixel p = InputImage.GetPixel(iR, iC);

                        p = p.Multiply(
                                KernelR.getKernelElementWithRelativePosition(kc, kr),
                                 KernelG.getKernelElementWithRelativePosition(kc, kr),
                                  KernelB.getKernelElementWithRelativePosition(kc, kr));

                        MPixel = MPixel.Add(p);
                        
                    }
                }
            }

            return MPixel   ;
        }

    }
}
